TCP/IP网络编程 —— 套接字(socket)

前言

做嵌入式物联网的,不学点网络编程好像有点说不过去,暑假学的RT-Thread的网络编程仅仅是了解了怎么用,可以说是知其然却不知其所以然,所以还是老老实实从基础开始,学一遍网络编程吧,挑了网友推荐的《TCP/IP网络编程》,感觉还不错。看了第一章,学习了Linux和Windows下的socket编程相关函数,本篇博客就简单的对Linux下的socket创建过程做下笔记。

网络编程

首先还是要介绍一下基础概念,所谓网络编程,简单来说就是编写程序使两台联网的计算机相互交换数据,而套接字呢,你可以理解为计算机连接网络的工具,进而引申为两台计算机之间的网络连接。
套接字大致还可以分为两种,一种是服务器端(server)的套接字,一种是客户端(client)的套接字,下面我们就对这两种套接字的创建过程及使用方法作详细的介绍。

服务器端(server)套接字

两台计算机要进行通信/数据交换,那么作为服务器端的计算机要做什么事情呢?自然是等待(监听)接受连接请求了,这个过程可归纳为以下四步:

第一步:调用socket函数创建套接字。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
-> 成功时返回文件描述符,失败时返回-1。

第二步:调用bind函数分配IP地址和端口号。

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
-> 成功时返回0,失败时返回-1。

第三步:调用listen函数转为可接收请求状态。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
-> 成功时返回0,失败时返回-1。

第四步:调用accept函数受理连接请求。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-> 成功时返回文件描述符,失败时返回-1。

客户端(client)套接字

那么客户端套接字,也就是请求连接服务器的套接字,它的创建比服务器端套接字简单多了,只有以下两步:

第一步:调用socket函数创建套接字。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
-> 成功时返回文件描述符,失败时返回-1。

第二步:调用connect函数向服务器端发送连接请求。

#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
-> 成功时返回0,失败时返回-1。

示例demo

通过调用上述介绍的函数,就可以编写自己的server/client程序进行模拟通讯啦,下面贴上两个Linux下的demo程序让大家理解整个过程的实现。

“Hello World!”服务器端demo

服务器端(server)是能够受理连接请求的程序。下面demo构建的服务器收到连接请求后向请求者返回“Hello World!”答复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/* hello_server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
int serv_sock;
int clnt_sock;

struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;

char message[]="Hello World!";

if(argc!=2)
{
printf("Usage : %s <port>\n",argv[0]);
exit(1);
}

serv_sock=socket(PF_INET, SOCK_STREAM, 0); //调用socket函数创建套接字。
if(serv_sock == -1)
error_handling("socket() error");

memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));

if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) //调用bind函数分配IP地址和端口号。
error_handling("bind() error");

if(listen(serv_sock, 5)==-1) //调用listen函数将套接字转为可接收连接状态。
error_handling("listen() error");

clnt_addr_size=sizeof(clnt_addr);
clnt_sock=accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size); //调用accept函数受理连接请求。如果在没有连接请求的的情况下调用该函数,则不会返回,直到有连接请求为止。
if(clnt_sock==-1)
error_handling("accept() error");

write(clnt_sock, message, sizeof(message)); //数据传输函数
close(clnt_sock);
close(serv_sock);
return 0;
}

void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n',stderr);
exit(1);
}

客户端demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* hello_client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;

if(argc!=3)
{
printf("Usage : %s <IP> <port>\n",argv[0]);
exit(1);
}

sock=socket(PF_INET, SOCK_STREAM, 0); //创建套接字,但此时套接字并不马上分为服务器端和客户端。如果紧接着调用bind,listen函数,将成为服务器端套接字;如果调用connect函数,将成为客户端套接字。
if(sock == -1)
error_handling("socket() error");

memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));

if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1) //调用connect函数向服务器端发送连接请求。
error_handling("connect() error");

str_len=read(sock, message, sizeof(message)-1);
if(str_len==-1)
error_handling("read() error");

printf("Message from server : %s \n", message);
close(sock);
return 0;
}

void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

运行

1
2
3
4
5
6
7
8
9
10
gcc hello_server.c -o hserver
->编译 hello_server.c文件并生成可执行文件hserver。
./hserver 9190
->运行当前目录下的hserver文件
运行结果:等待客户端连接。
gcc hello_client.c -o hclient
->编译......
./hclient 127.0.0.1 9190
->运行......
运行结果:Hello World!

本篇完结,谢谢观看,白了个白。。。